<!-- build time:Tue Feb 18 2020 17:19:27 GMT+0800 (China Standard Time) --><!DOCTYPE html><html lang="zh"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no,email=no,adress=no"><meta name="theme-color" content="#000000"><meta http-equiv="window-target" content="_top"><title>Spring Boot 异步任务入门 | calm&#39;s blog</title><meta name="description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Async-Job&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述异步调用，对应的是同步调用。同步调用：指程序按照 定义顺序 依次执行，每一行程序都必须等待上一行程序执行完成之后才能执行；异步调用：指程序在顺序执行时，不等待异步调用的语句返回结果，就执行后面的程序。考虑到异步调用的可"><meta property="og:type" content="article"><meta property="og:title" content="Spring Boot 异步任务入门"><meta property="og:url" content="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-Job/index.html"><meta property="og:site_name" content="卡姆"><meta property="og:description" content="摘要: 原创出处 http:&#x2F;&#x2F;www.iocoder.cn&#x2F;Spring-Boot&#x2F;Async-Job&#x2F; 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！1. 概述异步调用，对应的是同步调用。同步调用：指程序按照 定义顺序 依次执行，每一行程序都必须等待上一行程序执行完成之后才能执行；异步调用：指程序在顺序执行时，不等待异步调用的语句返回结果，就执行后面的程序。考虑到异步调用的可"><meta property="og:locale" content="zh_CN"><meta property="article:published_time" content="2020-02-18T05:10:39.000Z"><meta property="article:modified_time" content="2020-02-18T09:18:48.000Z"><meta property="article:author" content="calm"><meta property="article:tag" content="job"><meta name="twitter:card" content="summary"><link rel="canonical" href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-Job/index.html"><link rel="alternate" href="/atom.xml" title="卡姆" type="application/atom+xml"><link rel="icon" href="/blog/images/avatar.jpg" type="image/x-icon"><link rel="stylesheet" href="/blog/css/style.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.css"><meta name="generator" content="Hexo 4.2.0"></head><body class="main-center theme-green" itemscope itemtype="http://schema.org/WebPage"><header class="header" itemscope itemtype="http://schema.org/WPHeader"><div class="slimContent"><div class="navbar-header"><div class="profile-block text-center"><a id="avatar" href="https://github.com/yuyong725" target="_blank"><img class="img-circle img-rotate" src="/blog/images/avatar.jpg" width="200" height="200"></a><h2 id="name" class="hidden-xs hidden-sm">卡姆</h2><h3 id="title" class="hidden-xs hidden-sm hidden-md">a Java coder need calm</h3><small id="location" class="text-muted hidden-xs hidden-sm"><i class="icon icon-map-marker"></i> hangzhou, China</small></div><div class="search" id="search-form-wrap"><form class="search-form sidebar-form"><div class="input-group"><input type="text" class="search-form-input form-control" placeholder="搜索"> <span class="input-group-btn"><button type="submit" class="search-form-submit btn btn-flat" onclick="return!1"><i class="icon icon-search"></i></button></span></div></form><div class="ins-search"><div class="ins-search-mask"></div><div class="ins-search-container"><div class="ins-input-wrapper"><input type="text" class="ins-search-input" placeholder="想要查找什么..." x-webkit-speech> <button type="button" class="close ins-close ins-selectable" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="ins-section-wrapper"><div class="ins-section-container"></div></div></div></div></div><button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false"><span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span></button></div><nav id="main-navbar" class="collapse navbar-collapse" itemscope itemtype="http://schema.org/SiteNavigationElement" role="navigation"><ul class="nav navbar-nav main-nav"><li class="menu-item menu-item-home"><a href="/blog/."><i class="icon icon-home-fill"></i> <span class="menu-title">首页</span></a></li><li class="menu-item menu-item-archives"><a href="/blog/archives"><i class="icon icon-archives-fill"></i> <span class="menu-title">归档</span></a></li><li class="menu-item menu-item-categories"><a href="/blog/categories"><i class="icon icon-folder"></i> <span class="menu-title">分类</span></a></li><li class="menu-item menu-item-tags"><a href="/blog/tags"><i class="icon icon-tags"></i> <span class="menu-title">标签</span></a></li><li class="menu-item menu-item-repository"><a href="/blog/repository"><i class="icon icon-project"></i> <span class="menu-title">项目</span></a></li><li class="menu-item menu-item-books"><a href="/blog/books"><i class="icon icon-book-fill"></i> <span class="menu-title">书单</span></a></li><li class="menu-item menu-item-links"><a href="/blog/links"><i class="icon icon-friendship"></i> <span class="menu-title">友链</span></a></li><li class="menu-item menu-item-about"><a href="/blog/about"><i class="icon icon-cup-fill"></i> <span class="menu-title">关于</span></a></li></ul><ul class="social-links"><li><a href="https://github.com/yuyong725" target="_blank" title="Github" data-toggle="tooltip" data-placement="top"><i class="icon icon-github"></i></a></li></ul></nav></div></header><aside class="sidebar" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><div class="widget"><h3 class="widget-title">公告</h3><div class="widget-body"><div id="board"><div class="content"><p>欢迎交流与分享经验!</p></div></div></div></div><div class="widget"><h3 class="widget-title">分类</h3><div class="widget-body"><ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/blog/categories/CI-DI/">CI/DI</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/git/">git</a><span class="category-list-count">1</span></li><li class="category-list-item"><a class="category-list-link" href="/blog/categories/labs/">labs</a><span class="category-list-count">14</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签</h3><div class="widget-body"><ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/MyBatis/" rel="tag">MyBatis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Oauth2/" rel="tag">Oauth2</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/Spring-Security/" rel="tag">Spring Security</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/SpringMVC/" rel="tag">SpringMVC</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/docker/" rel="tag">docker</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/dubbo/" rel="tag">dubbo</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/git/" rel="tag">git</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jdbctemplate/" rel="tag">jdbctemplate</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jerkins/" rel="tag">jerkins</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/job/" rel="tag">job</a><span class="tag-list-count">2</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/jpa/" rel="tag">jpa</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/pdf/" rel="tag">pdf</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/redis/" rel="tag">redis</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" rel="tag">分库分表</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" rel="tag">多数据源</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" rel="tag">数据库版本管理</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" rel="tag">数据库连接池</a><span class="tag-list-count">1</span></li><li class="tag-list-item"><a class="tag-list-link" href="/blog/tags/%E7%BC%93%E5%AD%98/" rel="tag">缓存</a><span class="tag-list-count">2</span></li></ul></div></div><div class="widget"><h3 class="widget-title">标签云</h3><div class="widget-body tagcloud"><a href="/blog/tags/MyBatis/" style="font-size:13px">MyBatis</a> <a href="/blog/tags/Oauth2/" style="font-size:13px">Oauth2</a> <a href="/blog/tags/Spring-Security/" style="font-size:13px">Spring Security</a> <a href="/blog/tags/SpringMVC/" style="font-size:13px">SpringMVC</a> <a href="/blog/tags/docker/" style="font-size:13px">docker</a> <a href="/blog/tags/dubbo/" style="font-size:13px">dubbo</a> <a href="/blog/tags/git/" style="font-size:13px">git</a> <a href="/blog/tags/jdbctemplate/" style="font-size:13px">jdbctemplate</a> <a href="/blog/tags/jerkins/" style="font-size:13px">jerkins</a> <a href="/blog/tags/job/" style="font-size:14px">job</a> <a href="/blog/tags/jpa/" style="font-size:13px">jpa</a> <a href="/blog/tags/pdf/" style="font-size:13px">pdf</a> <a href="/blog/tags/redis/" style="font-size:13px">redis</a> <a href="/blog/tags/%E5%88%86%E5%BA%93%E5%88%86%E8%A1%A8/" style="font-size:13px">分库分表</a> <a href="/blog/tags/%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%BA%90/" style="font-size:13px">多数据源</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86/" style="font-size:13px">数据库版本管理</a> <a href="/blog/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BF%9E%E6%8E%A5%E6%B1%A0/" style="font-size:13px">数据库连接池</a> <a href="/blog/tags/%E7%BC%93%E5%AD%98/" style="font-size:14px">缓存</a></div></div><div class="widget"><h3 class="widget-title">归档</h3><div class="widget-body"><ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/02/">二月 2020</a><span class="archive-list-count">5</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2020/01/">一月 2020</a><span class="archive-list-count">8</span></li><li class="archive-list-item"><a class="archive-list-link" href="/blog/archives/2019/12/">十二月 2019</a><span class="archive-list-count">4</span></li></ul></div></div><div class="widget"><h3 class="widget-title">最新文章</h3><div class="widget-body"><ul class="recent-post-list list-unstyled no-thumbnail"><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="title">Spring Boot 异步任务入门</a></p><p class="item-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/18/labs-springboot2-job/" class="title">Spring Boot 定时任务入门</a></p><p class="item-date"><time datetime="2020-02-18T02:26:31.000Z" itemprop="datePublished">2020-02-18</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-database-version-control/" class="title">Spring Boot 数据库版本管理入门</a></p><p class="item-date"><time datetime="2020-02-17T12:05:54.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/17/labs-springboot2-sharding-datasource/" class="title">Spring Boot 分库分表入门</a></p><p class="item-date"><time datetime="2020-02-17T08:15:31.000Z" itemprop="datePublished">2020-02-17</time></p></div></li><li><div class="item-inner"><p class="item-category"><a class="category-link" href="/blog/categories/labs/">labs</a></p><p class="item-title"><a href="/blog/2020/02/13/labs-springboot2-dynamic-datasource/" class="title">Spring Boot 多数据源（读写分离）入门</a></p><p class="item-date"><time datetime="2020-02-13T03:18:37.000Z" itemprop="datePublished">2020-02-13</time></p></div></li></ul></div></div></div></aside><aside class="sidebar sidebar-toc collapse in" id="collapseToc" itemscope itemtype="http://schema.org/WPSideBar"><div class="slimContent"><nav id="toc" class="article-toc"><h3 class="toc-title">文章目录</h3><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-概述"><span class="toc-text">1. 概述</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#2-快速入门"><span class="toc-text">2. 快速入门</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#2-1-引入依赖"><span class="toc-text">2.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-2-AsyncSimpleApplication"><span class="toc-text">2.2 AsyncSimpleApplication</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-3-DemoService"><span class="toc-text">2.3 DemoService</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-4-同步调用测试"><span class="toc-text">2.4 同步调用测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-5-异步调用测试"><span class="toc-text">2.5 异步调用测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-6-等待异步调用完成测试"><span class="toc-text">2.6 等待异步调用完成测试</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-7-应用配置文件"><span class="toc-text">2.7 应用配置文件</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#3-异步回调"><span class="toc-text">3. 异步回调</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#3-1-AsyncResult"><span class="toc-text">3.1 AsyncResult</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-2-ListenableFutureTask"><span class="toc-text">3.2 ListenableFutureTask</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-3-具体示例"><span class="toc-text">3.3 具体示例</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#4-异步异常处理器"><span class="toc-text">4. 异步异常处理器</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#4-1-GlobalAsyncExceptionHandler"><span class="toc-text">4.1 GlobalAsyncExceptionHandler</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-2-AsyncConfig"><span class="toc-text">4.2 AsyncConfig</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-3-DemoService"><span class="toc-text">4.3 DemoService</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-4-简单测试"><span class="toc-text">4.4 简单测试</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#5-自定义执行器"><span class="toc-text">5. 自定义执行器</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#5-1-引入依赖"><span class="toc-text">5.1 引入依赖</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-2-应用配置文件"><span class="toc-text">5.2 应用配置文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-3-AsyncConfig"><span class="toc-text">5.3 AsyncConfig</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-4-DemoService"><span class="toc-text">5.4 DemoService</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5-5-简单测试"><span class="toc-text">5.5 简单测试</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#666-彩蛋"><span class="toc-text">666. 彩蛋</span></a></li></ol></nav></div></aside><main class="main" role="main"><div class="content"><article id="post-labs-springboot2-async-Job" class="article article-type-post" itemscope itemtype="http://schema.org/BlogPosting"><div class="article-header"><h1 class="article-title" itemprop="name">Spring Boot 异步任务入门</h1><div class="article-meta"><span class="article-date"><i class="icon icon-calendar-check"></i> <a href="/blog/2020/02/18/labs-springboot2-async-Job/" class="article-date"><time datetime="2020-02-18T05:10:39.000Z" itemprop="datePublished">2020-02-18</time></a></span> <span class="article-category"><i class="icon icon-folder"></i> <a class="article-category-link" href="/blog/categories/labs/">labs</a></span> <span class="article-tag"><i class="icon icon-tags"></i> <a class="article-tag-link" href="/blog/tags/job/" rel="tag">job</a></span> <span class="article-read hidden-xs"><i class="icon icon-eye-fill" aria-hidden="true"></i> <span id="busuanzi_container_page_pv"><span id="busuanzi_value_page_pv">0</span></span></span> <span class="post-comment"><i class="icon icon-comment"></i> <a href="/blog/2020/02/18/labs-springboot2-async-Job/#comments" class="article-comment-link">评论</a></span> <span class="post-wordcount hidden-xs" itemprop="wordCount">字数统计: 7.7k(字)</span> <span class="post-readcount hidden-xs" itemprop="timeRequired">阅读时长: 33(分)</span></div></div><div class="article-entry marked-body" itemprop="articleBody"><blockquote><p>摘要: 原创出处 <a href="http://www.iocoder.cn/Spring-Boot/Async-Job/" target="_blank" rel="noopener">http://www.iocoder.cn/Spring-Boot/Async-Job/</a> 「芋道源码」，卡姆在此基础上略作修改，欢迎转载，保留摘要，谢谢！</p></blockquote><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><blockquote><p>异步调用，对应的是同步调用。</p><ul><li>同步调用：指程序按照 定义顺序 依次执行，每一行程序都必须等待上一行程序执行完成之后才能执行；</li><li>异步调用：指程序在顺序执行时，不等待异步调用的语句返回结果，就执行后面的程序。</li></ul></blockquote><p>考虑到异步调用的<strong>可靠性</strong>，我们一般会考虑引入分布式消息队列，例如说 RabbitMQ、RocketMQ、Kafka 等等。但是在一些时候，我们并不需要这么高的可靠性，可以使用<strong>进程内</strong>的队列或者线程池。例如说示例代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Demo</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 创建线程池。这里只是临时测试，不要扣阿里 Java 开发规范，YEAH</span></span><br><span class="line">        ExecutorService executor = Executors.newFixedThreadPool(<span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 提交任务到线程池中执行。</span></span><br><span class="line">        executor.submit(<span class="keyword">new</span> Runnable() &#123;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                System.out.println(<span class="string">"听说我被异步调用了"</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>友情提示：这里说<strong>进程内</strong>的队列或者线程池，相对<strong>不可靠</strong>的原因是，队列和线程池中的任务仅仅存储在内存中，如果 JVM 进程被异常关闭，将会导致丢失，未被执行。</p><p>而分布式消息队列，异步调用会以一个消息的形式，存储在消息队列的服务器上，所以即使 JVM 进程被异常关闭，消息依然在消息队列的服务器上。</p><p>所以，使用<strong>进程内</strong>的队列或者线程池来实现异步调用的话，一定要尽可能的保证 JVM 进程的优雅关闭，保证它们在关闭前被执行完成。</p></blockquote><p>在 <a href="https://github.com/spring-projects/spring-framework" target="_blank" rel="noopener">Spring Framework</a> 的 <a href="https://github.com/spring-projects/spring-framework/tree/master/spring-core/src/main/java/org/springframework/core/task" target="_blank" rel="noopener">Spring Task</a> 模块，提供了 <code>@Async</code> 注解，可以添加在方法上，自动实现该方法的异步调用。</p><blockquote><p>如果胖友看过卡姆写的 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-job/">《Spring Boot 定时任务入门》</a> 文章，就会发现 Spring Task 模块，还提供了定时任务的功能。</p></blockquote><p>下面，让我们一起遨游 Spring 异步任务的海洋。</p><h1 id="2-快速入门"><a href="#2-快速入门" class="headerlink" title="2. 快速入门"></a>2. 快速入门</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-simple" target="_blank" rel="noopener">labs-springboot2-async-simple</a> 。</p></blockquote><p>本小节，我们会编写示例，对比同步调用和异步调用的性能差别，并演示 Spring <code>@Async</code> 注解的使用方式。</p><h2 id="2-1-引入依赖"><a href="#2-1-引入依赖" class="headerlink" title="2.1 引入依赖"></a>2.1 引入依赖</h2><p>在 <code>pom.xml</code> 文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-async-simple<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 引入 Spring Boot 依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><p>因为 Spring Task 是 Spring Framework 的模块，所以在我们引入 <code>spring-boot-web</code> 依赖后，无需特别引入它。</p><h2 id="2-2-AsyncSimpleApplication"><a href="#2-2-AsyncSimpleApplication" class="headerlink" title="2.2 AsyncSimpleApplication"></a>2.2 AsyncSimpleApplication</h2><p>创建 <code>AsyncSimpleApplication.java</code> 类，配置 <code>@SpringBootApplication</code> 注解。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableAsync</span> <span class="comment">// 开启 @Async 的支持</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AsyncSimpleApplication</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application<span class="class">.<span class="keyword">class</span>, <span class="title">args</span>)</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在类上添加 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java" target="_blank" rel="noopener">@EnableAsync</a> 注解，启用异步功能。</li></ul><h2 id="2-3-DemoService"><a href="#2-3-DemoService" class="headerlink" title="2.3 DemoService"></a>2.3 DemoService</h2><p>在 <code>cn.javadog.labs.springboot2.async.simple.service</code> 包路径下，创建 <code>DemoService</code> 类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> Integer <span class="title">execute01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[execute01]"</span>);</span><br><span class="line">        sleep(<span class="number">10</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Integer <span class="title">execute02</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[execute02]"</span>);</span><br><span class="line">        sleep(<span class="number">5</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sleep</span><span class="params">(<span class="keyword">int</span> seconds)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(seconds * <span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(e);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>定义了 <code>#execute01()</code> 和 <code>#execute02()</code> 方法，分别 sleep 10 秒和 5 秒，模拟耗时操作。</li><li>同时在每个方法里，使用 <code>logger</code> 打印日志，方便我们看到每个方法的开始执行时间，和执行所在<strong>线程</strong>。</li></ul><h2 id="2-4-同步调用测试"><a href="#2-4-同步调用测试" class="headerlink" title="2.4 同步调用测试"></a>2.4 同步调用测试</h2><p>创建 <code>DemoServiceTest</code> 测试类，编写 <code>#task01()</code> 方法，同步调用 DemoService 的上述两个方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span>(<span class="title">classes</span> </span>= AsyncSimpleApplication<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">DemoServiceTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoService demoService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">task01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line">        logger.info(<span class="string">"[task01][开始执行]"</span>);</span><br><span class="line"></span><br><span class="line">        demoService.execute01();</span><br><span class="line">        demoService.execute02();</span><br><span class="line"></span><br><span class="line">        logger.info(<span class="string">"[task01][结束执行，消耗时长 &#123;&#125; 毫秒]"</span>, System.currentTimeMillis() - now);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行单元测试，执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">45</span>:<span class="number">12</span><span class="variable">.453</span>  INFO <span class="number">20557</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task01][开始执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">45</span>:<span class="number">12</span><span class="variable">.465</span>  INFO <span class="number">20557</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute01]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">45</span>:<span class="number">22</span><span class="variable">.466</span>  INFO <span class="number">20557</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute02]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">45</span>:<span class="number">27</span><span class="variable">.471</span>  INFO <span class="number">20557</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task01][结束执行，消耗时长 <span class="number">15017</span> 毫秒]</span><br></pre></td></tr></table></figure><ul><li>DemoService 的两个方法，顺序执行，一共消耗 15 秒左右。</li><li>DemoService 的两个方法，都在<strong>主线程</strong>中执行。</li></ul><h2 id="2-5-异步调用测试"><a href="#2-5-异步调用测试" class="headerlink" title="2.5 异步调用测试"></a>2.5 异步调用测试</h2><p>修改 DemoService 的代码，增加 <code>#execute01()</code> 和 <code>#execute02()</code> 的异步调用。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Integer <span class="title">execute01Async</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.execute01();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Integer <span class="title">execute02Async</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.execute02();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>额外增加了 <code>#execute01Async()</code> 和 <code>#execute02Async()</code> 方法，主要是不想破坏上面的「2.4 同步调用测试」哈。实际上，可以在 <code>#execute01()</code> 和 <code>#execute02()</code> 方法上，添加 <code>@Async</code> 注解，实现异步调用。</li></ul><p>修改 <code>DemoServiceTest</code> 测试类，编写 <code>#task02()</code> 方法，异步调用上述的两个方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">task02</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line">    logger.info(<span class="string">"[task02][开始执行]"</span>);</span><br><span class="line"></span><br><span class="line">    demoService.execute01Async();</span><br><span class="line">    demoService.execute02Async();</span><br><span class="line"></span><br><span class="line">    logger.info(<span class="string">"[task02][结束执行，消耗时长 &#123;&#125; 毫秒]"</span>, System.currentTimeMillis() - now);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行单元测试，执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">48</span>:<span class="number">24</span><span class="variable">.800</span>  INFO <span class="number">20565</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task02][开始执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">48</span>:<span class="number">24</span><span class="variable">.828</span>  INFO <span class="number">20565</span> --- [           main] o<span class="variable">.s</span><span class="variable">.s</span><span class="variable">.concurrent</span><span class="variable">.ThreadPoolTaskExecutor</span>  : Initializing ExecutorService 'applicationTaskExecutor'</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">48</span>:<span class="number">24</span><span class="variable">.843</span>  INFO <span class="number">20565</span> --- [   async-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute01]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">48</span>:<span class="number">24</span><span class="variable">.843</span>  INFO <span class="number">20565</span> --- [   async-<span class="keyword">task</span>-<span class="number">2</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute02]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">48</span>:<span class="number">24</span><span class="variable">.844</span>  INFO <span class="number">20565</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task02][结束执行，消耗时长 <span class="number">44</span> 毫秒]</span><br></pre></td></tr></table></figure><ul><li>DemoService 的两个方法，异步执行，所以主线程只消耗 44 毫秒左右。<strong>注意</strong>，实际这两个方法，并没有执行完成。</li><li>DemoService 的两个方法，都在<strong>异步的线程池</strong>中，进行执行。</li></ul><h2 id="2-6-等待异步调用完成测试"><a href="#2-6-等待异步调用完成测试" class="headerlink" title="2.6 等待异步调用完成测试"></a>2.6 等待异步调用完成测试</h2><p>在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.5 异步调用测试」</a> 中，两个方法只是发布异步调用，并未执行完成。在一些业务场景中，我们希望达到异步调用的效果，同时主线程<strong>阻塞等待</strong>异步调用的结果。</p><p>修改 DemoService 的代码，增加 <code>#execute01()</code> 和 <code>#execute02()</code> 的异步调用，<strong>并返回 Future 对象</strong>。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Future&lt;Integer&gt; <span class="title">execute01AsyncWithFuture</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> AsyncResult.forValue(<span class="keyword">this</span>.execute01());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Future&lt;Integer&gt; <span class="title">execute02AsyncWithFuture</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> AsyncResult.forValue(<span class="keyword">this</span>.execute02());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>相比 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.5 异步调用测试」</a> 的两个方法，我们额外增加调用 <code>AsyncResult#forValue(V value)</code> 方法，返回带有执行结果的 Future 对象。</li></ul><p>修改 <code>DemoServiceTest</code> 测试类，编写 <code>#task03()</code> 方法，异步调用上述的两个方法，<strong>并阻塞等待执行完成</strong>。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">task03</span><span class="params">()</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line">    logger.info(<span class="string">"[task03][开始执行]"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;1&gt; 执行任务</span></span><br><span class="line">    Future&lt;Integer&gt; execute01Result = demoService.execute01AsyncWithFuture();</span><br><span class="line">    Future&lt;Integer&gt; execute02Result = demoService.execute02AsyncWithFuture();</span><br><span class="line">    <span class="comment">// &lt;2&gt; 阻塞等待结果</span></span><br><span class="line">    execute01Result.get();</span><br><span class="line">    execute02Result.get();</span><br><span class="line"></span><br><span class="line">    logger.info(<span class="string">"[task03][结束执行，消耗时长 &#123;&#125; 毫秒]"</span>, System.currentTimeMillis() - now);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，异步调用两个方法，并返回对应的 Future 对象。这样，这两个异步调用的逻辑，可以<strong>并行</strong>执行。</li><li><code>&lt;2&gt;</code> 处，分别调用两个 Future 对象的 <code>#get()</code> 方法，阻塞等待结果。</li></ul><p>运行单元测试，执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">51</span>:<span class="number">34</span><span class="variable">.000</span>  INFO <span class="number">20572</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task03][开始执行]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">51</span>:<span class="number">34</span><span class="variable">.032</span>  INFO <span class="number">20572</span> --- [           main] o<span class="variable">.s</span><span class="variable">.s</span><span class="variable">.concurrent</span><span class="variable">.ThreadPoolTaskExecutor</span>  : Initializing ExecutorService 'applicationTaskExecutor'</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">51</span>:<span class="number">34</span><span class="variable">.049</span>  INFO <span class="number">20572</span> --- [   async-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute01]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">51</span>:<span class="number">34</span><span class="variable">.049</span>  INFO <span class="number">20572</span> --- [   async-<span class="keyword">task</span>-<span class="number">2</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span>     : [execute02]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">16</span>:<span class="number">51</span>:<span class="number">44</span><span class="variable">.053</span>  INFO <span class="number">20572</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task03][结束执行，消耗时长 <span class="number">10053</span> 毫秒]</span><br></pre></td></tr></table></figure><ul><li>DemoService 的两个方法，异步执行，因为主线程阻塞等待执行结果，所以消耗 10 秒左右。<strong>当同时有多个异步调用，并阻塞等待执行结果，消耗时长由最慢的异步调用的逻辑所决定。</strong></li><li>DemoService 的两个方法，都在<strong>异步的线程池</strong>中，进行执行。</li></ul><blockquote><p>下面<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.7 应用配置文件」</a>小节，是补充知识，建议看看。</p></blockquote><h2 id="2-7-应用配置文件"><a href="#2-7-应用配置文件" class="headerlink" title="2.7 应用配置文件"></a>2.7 应用配置文件</h2><p>在 <code>application.yaml</code> 中，添加 Spring Task 定时任务的配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">task:</span></span><br><span class="line">    <span class="comment"># Spring 执行器配置，对应 TaskExecutionProperties 配置类。对于 Spring 异步任务，会使用该执行器。</span></span><br><span class="line">    <span class="attr">execution:</span></span><br><span class="line">      <span class="attr">thread-name-prefix:</span> <span class="string">async-task-</span> <span class="comment"># 线程池的线程名的前缀。默认为 task- ，建议根据自己应用来设置</span></span><br><span class="line">      <span class="attr">pool:</span> <span class="comment"># 线程池相关</span></span><br><span class="line">        <span class="attr">core-size:</span> <span class="number">8</span> <span class="comment"># 核心线程数，线程池创建时候初始化的线程数。默认为 8 。</span></span><br><span class="line">        <span class="attr">max-size:</span> <span class="number">20</span> <span class="comment"># 最大线程数，线程池最大的线程数，只有在缓冲队列满了之后，才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE</span></span><br><span class="line">        <span class="attr">keep-alive:</span> <span class="string">60s</span> <span class="comment"># 允许线程的空闲时间，当超过了核心线程之外的线程，在空闲时间到达之后会被销毁。默认为 60 秒</span></span><br><span class="line">        <span class="attr">queue-capacity:</span> <span class="number">200</span> <span class="comment"># 缓冲队列大小，用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。</span></span><br><span class="line">        <span class="attr">allow-core-thread-timeout:</span> <span class="literal">true</span> <span class="comment"># 是否允许核心线程超时，即开启线程池的动态增长和缩小。默认为 true 。</span></span><br><span class="line">      <span class="attr">shutdown:</span></span><br><span class="line">        <span class="attr">await-termination:</span> <span class="literal">true</span> <span class="comment"># 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span></span><br><span class="line">        <span class="attr">await-termination-period:</span> <span class="number">60</span> <span class="comment"># 等待任务完成的最大时长，单位为秒。默认为 0 ，根据自己应用来设置</span></span><br></pre></td></tr></table></figure><ul><li>在 <code>spring.task.execution</code> 配置项，Spring Task 调度任务的配置，对应 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionProperties.java" target="_blank" rel="noopener">TaskExecutionProperties</a> 配置类。</li><li>Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java" target="_blank" rel="noopener">TaskExecutionAutoConfiguration</a> 自动化配置类，实现 Spring Task 的自动配置，创建 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.java" target="_blank" rel="noopener">ThreadPoolTaskExecutor</a> 基于线程池的任务执行器。本质上，ThreadPoolTaskExecutor 是基于 ThreadPoolExecutor 的封装，主要增加提交任务，返回 <strong>ListenableFuture</strong> 对象的功能。</li></ul><p><strong>注意</strong>，<code>spring.task.execution.shutdown</code> 配置项，是为了实现 Spring Task 异步任务的优雅关闭。我们想象一下，如果异步任务在执行的过程中，如果应用开始关闭，把异步任务需要使用到的 Spring Bean 进行销毁，例如说数据库连接池，那么此时异步任务还在执行中，一旦需要访问数据库，可能会导致报错。</p><ul><li>所以，通过配置 <code>await-termination = true</code> ，实现应用关闭时，等待异步任务执行完成。这样，应用在关闭的时，Spring 会优先等待 ThreadPoolTaskScheduler 执行完任务之后，再开始 Spring Bean 的销毁。</li><li>同时，又考虑到我们不可能无限等待异步任务全部执行结束，因此可以配置 <code>await-termination-period = 60</code> ，等待任务完成的最大时长，单位为秒。具体设置多少的等待时长，可以根据自己应用的需要。</li></ul><h1 id="3-异步回调"><a href="#3-异步回调" class="headerlink" title="3. 异步回调"></a>3. 异步回调</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-simple" target="_blank" rel="noopener">labs-springboot2-async-simple</a> 。</p></blockquote><p>异步 + 回调，快活似神仙。所以本小节我们来看看，如何在异步调用完成后，实现<strong>自定义回调</strong>。</p><blockquote><p>友情提示：该示例，基于 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2. 快速入门」</a> 的 <a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-simple" target="_blank" rel="noopener">labs-springboot2-async-simple</a> 的基础上，继续改造。</p></blockquote><h2 id="3-1-AsyncResult"><a href="#3-1-AsyncResult" class="headerlink" title="3.1 AsyncResult"></a>3.1 AsyncResult</h2><p>在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.6 等待异步调用完成测试」</a> 中，我们看到了 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncResult.java" target="_blank" rel="noopener">AsyncResult</a> 类，表示异步结果。返回结果分成两种情况：</p><ul><li><p>执行成功时，调用 <code>AsyncResult#forValue(V value)</code> 静态方法，返回成功的 ListenableFuture 对象。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncResult.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> V value;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;V&gt; <span class="function">ListenableFuture&lt;V&gt; <span class="title">forValue</span><span class="params">(V value)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">new</span> AsyncResult&lt;&gt;(value, <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>执行异常时，调用 <code>AsyncResult#forExecutionException(Throwable ex)</code> 静态方法，返回异常的 ListenableFuture 对象。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncResult.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Throwable executionException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;V&gt; <span class="function">ListenableFuture&lt;V&gt; <span class="title">forExecutionException</span><span class="params">(Throwable ex)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">new</span> AsyncResult&lt;&gt;(<span class="keyword">null</span>, ex);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li></ul><p>同时，AsyncResult 实现了 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java" target="_blank" rel="noopener">ListenableFuture</a> 接口，提供异步执行结果的回调处理。这里，我们先来看看 ListenableFuture 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListenableFuture.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ListenableFuture</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">Future</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加回调方法，统一处理成功和异常的情况。</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(ListenableFutureCallback&lt;? <span class="keyword">super</span> T&gt; callback)</span></span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 添加成功和失败的回调方法，分别处理成功和异常的情况。</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(SuccessCallback&lt;? <span class="keyword">super</span> T&gt; successCallback, FailureCallback failureCallback)</span></span>;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 将 ListenableFuture 转换成 JDK8 提供的 CompletableFuture 。</span></span><br><span class="line">    <span class="comment">// 这样，后续我们可以使用 ListenableFuture 来设置回调</span></span><br><span class="line">    <span class="comment">// 不了解 CompletableFuture 的胖友，可以看看 https://colobu.com/2016/02/29/Java-CompletableFuture/ 文章。</span></span><br><span class="line">    <span class="function"><span class="keyword">default</span> CompletableFuture&lt;T&gt; <span class="title">completable</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    	CompletableFuture&lt;T&gt; completable = <span class="keyword">new</span> DelegatingCompletableFuture&lt;&gt;(<span class="keyword">this</span>);</span><br><span class="line">    	addCallback(completable::complete, completable::completeExceptionally);</span><br><span class="line">    	<span class="keyword">return</span> completable;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>看下每个接口方法上的注释。</li></ul><p>因为 ListenableFuture 继承了 <a href="https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/concurrent/Future.java" target="_blank" rel="noopener">Future</a> 接口，所以 AsyncResult 也需要实现 Future 接口。这里，我们再来看看 Future 接口。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Future.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Future</span>&lt;<span class="title">V</span>&gt; </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取异步执行的结果，如果没有结果可用，此方法会阻塞直到异步计算完成。</span></span><br><span class="line">    <span class="function">V <span class="title">get</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException, ExecutionException</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取异步执行结果，如果没有结果可用，此方法会阻塞，但是会有时间限制，如果阻塞时间超过设定的 timeout 时间，该方法将抛出异常。</span></span><br><span class="line">    <span class="function">V <span class="title">get</span><span class="params">(<span class="keyword">long</span> timeout, TimeUnit unit)</span></span></span><br><span class="line"><span class="function">        <span class="keyword">throws</span> InterruptedException, ExecutionException, TimeoutException</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果任务执行结束，无论是正常结束或是中途取消还是发生异常，都返回 true 。</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">isDone</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果任务完成前被取消，则返回 true 。</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">isCancelled</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 如果任务还没开始，执行 cancel(...) 方法将返回 false；</span></span><br><span class="line">    <span class="comment">// 如果任务已经启动，执行 cancel(true) 方法将以中断执行此任务线程的方式来试图停止任务，如果停止成功，返回 true ；</span></span><br><span class="line">    <span class="comment">// 当任务已经启动，执行c ancel(false) 方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成)，此时返回 false ；</span></span><br><span class="line">    <span class="comment">// 当任务已经完成，执行 cancel(...) 方法将返回 false 。</span></span><br><span class="line">    <span class="comment">// mayInterruptRunning 参数表示是否中断执行中的线程。</span></span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">cancel</span><span class="params">(<span class="keyword">boolean</span> mayInterruptIfRunning)</span></span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>如上注释内容，参考自 <a href="https://blog.csdn.net/javazejian/article/details/50896505" target="_blank" rel="noopener">《Java 多线程编程：Callable、Future 和 FutureTask 浅析》</a> 文章。</li></ul><p>AsyncResult 对 ListenableFuture 定义的 <code>#addCallback(...)</code> 接口方法，实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncResult.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(ListenableFutureCallback&lt;? <span class="keyword">super</span> V&gt; callback)</span> </span>&#123;</span><br><span class="line">	addCallback(callback, callback);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(SuccessCallback&lt;? <span class="keyword">super</span> V&gt; successCallback, FailureCallback failureCallback)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		<span class="keyword">if</span> (<span class="keyword">this</span>.executionException != <span class="keyword">null</span>) &#123; <span class="comment">// &lt;1&gt;</span></span><br><span class="line">			failureCallback.onFailure(exposedException(<span class="keyword">this</span>.executionException));</span><br><span class="line">		&#125; <span class="keyword">else</span> &#123; <span class="comment">// &lt;2&gt;</span></span><br><span class="line">			successCallback.onSuccess(<span class="keyword">this</span>.value);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125; <span class="keyword">catch</span> (Throwable ex) &#123; <span class="comment">// &lt;3&gt;</span></span><br><span class="line">		<span class="comment">// Ignore</span></span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 从 ExecutionException 中，获得原始异常。</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Throwable <span class="title">exposedException</span><span class="params">(Throwable original)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (original <span class="keyword">instanceof</span> ExecutionException) &#123;</span><br><span class="line">		Throwable cause = original.getCause();</span><br><span class="line">		<span class="keyword">if</span> (cause != <span class="keyword">null</span>) &#123;</span><br><span class="line">			<span class="keyword">return</span> cause;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> original;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><a href="https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java" target="_blank" rel="noopener">ListenableFutureCallback</a> 接口，同时继承 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java" target="_blank" rel="noopener">SuccessCallback</a> 和 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java" target="_blank" rel="noopener">FailureCallback</a> 接口。</li><li><code>&lt;1&gt;</code> 处，如果是异常的结果，调用 FailureCallback 的回调。</li><li><code>&lt;2&gt;</code> 处，如果是正常的结果，调用 SuccessCallback 的回调。</li><li><code>&lt;3&gt;</code> 处，如果回调的逻辑发生异常，直接忽略。所有，如果如果有多个回调，如果有一个回调发生异常，不会影响后续的回调。</li></ul><p>(⊙o⊙)… 不过有点懵逼的是，不是应该在异步调用执行成功后，才进行回调么？！怎么这里一添加回调方法，就直接执行了？！不要着急，答案在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「3.2 ListenableFutureTask」</a> 中解答。</p><p>实际上，AsyncResult 是作为异步执行的<strong>结果</strong>。既然是结果，执行就已经完成。所以，在我们调用 <code>#addCallback(...)</code> 接口方法来添加回调时，<strong>必然直接使用回调处理执行的结果</strong>。</p><p>AsyncResult 对 ListenableFuture 定义的 <code>#completable(...)</code> 接口方法，实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncResult.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> CompletableFuture&lt;V&gt; <span class="title">completable</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (<span class="keyword">this</span>.executionException != <span class="keyword">null</span>) &#123;</span><br><span class="line">		CompletableFuture&lt;V&gt; completable = <span class="keyword">new</span> CompletableFuture&lt;&gt;();</span><br><span class="line">		completable.completeExceptionally(exposedException(<span class="keyword">this</span>.executionException));</span><br><span class="line">		<span class="keyword">return</span> completable;</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> CompletableFuture.completedFuture(<span class="keyword">this</span>.value);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>直接将结果包装成 CompletableFuture 对象。</li></ul><p>AsyncResult 对 Future 定义的所有方法，实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncResult.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">cancel</span><span class="params">(<span class="keyword">boolean</span> mayInterruptIfRunning)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">false</span>; <span class="comment">// 因为是 AsyncResult 是执行结果，所以直接返回 false 表示取消失败。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isCancelled</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">false</span>; <span class="comment">// 因为是 AsyncResult 是执行结果，所以直接返回 false 表示未取消。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isDone</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">true</span>; <span class="comment">// 因为是 AsyncResult 是执行结果，所以直接返回 true 表示已完成。</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">()</span> <span class="keyword">throws</span> ExecutionException </span>&#123;</span><br><span class="line">	<span class="comment">// 如果发生异常，则抛出该异常。</span></span><br><span class="line">	<span class="keyword">if</span> (<span class="keyword">this</span>.executionException != <span class="keyword">null</span>) &#123;</span><br><span class="line">		<span class="keyword">throw</span> (<span class="keyword">this</span>.executionException <span class="keyword">instanceof</span> ExecutionException ?</span><br><span class="line">				(ExecutionException) <span class="keyword">this</span>.executionException :</span><br><span class="line">				<span class="keyword">new</span> ExecutionException(<span class="keyword">this</span>.executionException));</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">// 如果执行成功，则返回该 value 结果</span></span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">this</span>.value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="meta">@Nullable</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> V <span class="title">get</span><span class="params">(<span class="keyword">long</span> timeout, TimeUnit unit)</span> <span class="keyword">throws</span> ExecutionException </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> get();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>胖友自己看看代码上的注释。</li></ul><p>看到这里，相信很多胖友会是一脸懵逼，淡定淡定。看源码这个事儿，总是柳暗花明又一村。</p><h2 id="3-2-ListenableFutureTask"><a href="#3-2-ListenableFutureTask" class="headerlink" title="3.2 ListenableFutureTask"></a>3.2 ListenableFutureTask</h2><blockquote><p>感兴趣的胖友，可以看看 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionInterceptor.java" target="_blank" rel="noopener">AsyncExecutionInterceptor</a> 类、<a href="https://zhuanlan.zhihu.com/p/59633595" target="_blank" rel="noopener">《Spring 异步调用原理及Spring AOP 拦截器链原理》</a> 文章。</p></blockquote><p>ListenableFutureTask 类，<strong>也实现 ListenableFuture 接口</strong>，继承 <a href="https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/util/concurrent/FutureTask.java" target="_blank" rel="noopener">FutureTask</a> 类，ListenableFuture 的 FutureTask 实现类。</p><p>ListenableFutureTask 对 ListenableFuture 定义的 <code>#addCallback(...)</code> 方法，实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListenableFutureTask.java</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ListenableFutureCallbackRegistry&lt;T&gt; callbacks = <span class="keyword">new</span> ListenableFutureCallbackRegistry&lt;T&gt;();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(ListenableFutureCallback&lt;? <span class="keyword">super</span> T&gt; callback)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">this</span>.callbacks.addCallback(callback);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(SuccessCallback&lt;? <span class="keyword">super</span> T&gt; successCallback, FailureCallback failureCallback)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">this</span>.callbacks.addSuccessCallback(successCallback);</span><br><span class="line">	<span class="keyword">this</span>.callbacks.addFailureCallback(failureCallback);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>暂存回调到 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java" target="_blank" rel="noopener">ListenableFutureCallbackRegistry</a> 中先。这样看起来，和我们想象中的异步回调有点像了。</li></ul><p>ListenableFutureTask 对 FutureTask 已实现的 <code>#done()</code> 方法，进行<strong>重写</strong>。实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ListenableFutureTask.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">done</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	Throwable cause;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">	   <span class="comment">// &lt;1&gt; 获得执行结果</span></span><br><span class="line">		T result = get();</span><br><span class="line">		<span class="comment">// &lt;2.1&gt; 执行成功，执行成功的回调</span></span><br><span class="line">		<span class="keyword">this</span>.callbacks.success(result);</span><br><span class="line">		<span class="keyword">return</span>;</span><br><span class="line">	&#125; <span class="keyword">catch</span> (InterruptedException ex) &#123; <span class="comment">// 如果有中断异常 InterruptedException ，则打断当前线程，并直接返回</span></span><br><span class="line">		Thread.currentThread().interrupt();</span><br><span class="line">		<span class="keyword">return</span>;</span><br><span class="line">	&#125; <span class="keyword">catch</span> (ExecutionException ex) &#123; <span class="comment">// 如果有 ExecutionException 异常，获得其真实的异常，并设置到 cause 中</span></span><br><span class="line">		cause = ex.getCause();</span><br><span class="line">		<span class="keyword">if</span> (cause == <span class="keyword">null</span>) &#123;</span><br><span class="line">			cause = ex;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125; <span class="keyword">catch</span> (Throwable ex) &#123; <span class="comment">// 设置异常到 cause 中</span></span><br><span class="line">		cause = ex;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">// 执行异常，执行异常的回调</span></span><br><span class="line">	<span class="keyword">this</span>.callbacks.failure(cause);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><code>&lt;1&gt;</code> 处，调用 <code>#get()</code> 方法，获得执行结果。</li><li><code>&lt;2.1&gt;</code> 处，执行成功，执行成功的回调。</li><li><code>&lt;2.2&gt;</code> 处，执行异常，执行异常的回调。</li></ul><p>这样一看，是不是对 AsyncResult 和 ListenableFutureTask 就有点感觉了。</p><h2 id="3-3-具体示例"><a href="#3-3-具体示例" class="headerlink" title="3.3 具体示例"></a>3.3 具体示例</h2><p>下面，让我们来写一个异步回调的示例。修改 DemoService 的代码，增加 <code>#execute02()</code> 的异步调用，<strong>并返回 ListenableFuture 对象</strong>。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> ListenableFuture&lt;Integer&gt; <span class="title">execute01AsyncWithListenableFuture</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> AsyncResult.forValue(<span class="keyword">this</span>.execute02());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">        <span class="keyword">return</span> AsyncResult.forExecutionException(ex);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>根据执行的结果，包装出成功还是异常的 AsyncResult 对象。</li></ul><p>修改 <code>DemoServiceTest</code> 测试类，编写 <code>#task04()</code> 方法，异步调用上述的方法，在塞等待执行完成的同时，添加相应的回调 Callback 方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">task04</span><span class="params">()</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>&#123;</span><br><span class="line">    <span class="keyword">long</span> now = System.currentTimeMillis();</span><br><span class="line">    logger.info(<span class="string">"[task04][开始执行]"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// &lt;1&gt; 执行任务</span></span><br><span class="line">    ListenableFuture&lt;Integer&gt; execute01Result = demoService.execute01AsyncWithListenableFuture();</span><br><span class="line">    logger.info(<span class="string">"[task04][execute01Result 的类型是：(&#123;&#125;)]"</span>,execute01Result.getClass().getSimpleName());</span><br><span class="line">    execute01Result.addCallback(<span class="keyword">new</span> SuccessCallback&lt;Integer&gt;() &#123; <span class="comment">// &lt;2.1&gt; 增加成功的回调</span></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSuccess</span><span class="params">(Integer result)</span> </span>&#123;</span><br><span class="line">            logger.info(<span class="string">"[onSuccess][result: &#123;&#125;]"</span>, result);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;, <span class="keyword">new</span> FailureCallback() &#123; <span class="comment">// &lt;2.1&gt; 增加失败的回调</span></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFailure</span><span class="params">(Throwable ex)</span> </span>&#123;</span><br><span class="line">            logger.info(<span class="string">"[onFailure][发生异常]"</span>, ex);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;);</span><br><span class="line">    execute01Result.addCallback(<span class="keyword">new</span> ListenableFutureCallback&lt;Integer&gt;() &#123; <span class="comment">// &lt;2.2&gt; 增加成功和失败的统一回调</span></span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onSuccess</span><span class="params">(Integer result)</span> </span>&#123;</span><br><span class="line">            logger.info(<span class="string">"[onSuccess][result: &#123;&#125;]"</span>, result);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onFailure</span><span class="params">(Throwable ex)</span> </span>&#123;</span><br><span class="line">            logger.info(<span class="string">"[onFailure][发生异常]"</span>, ex);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">// &lt;3&gt; 阻塞等待结果</span></span><br><span class="line">    execute01Result.get();</span><br><span class="line"></span><br><span class="line">    logger.info(<span class="string">"[task04][结束执行，消耗时长 &#123;&#125; 毫秒]"</span>, System.currentTimeMillis() - now);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><p><code>&lt;1&gt;</code> 处，调用 <code>DemoService#execute01AsyncWithListenableFuture()</code> 方法，异步调用该方法，并返回 ListenableFutureTask 对象。这里，我们看下打印的日志。</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">01</span>:<span class="number">44</span><span class="variable">.844</span>  INFO <span class="number">20589</span> --- [           main] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [task04][execute01Result 的类型是：(ListenableFutureTask)]</span><br></pre></td></tr></table></figure></li><li><p><code>&lt;2.1&gt;</code> 处，增加成功的回调和失败的回调。</p></li><li><p><code>&lt;2.2&gt;</code> 处，增加成功和失败的统一回调。</p></li><li><p><code>&lt;3&gt;</code> 处，阻塞等待结果。执行完成后，我们会看到回调被执行，打印日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">01</span>:<span class="number">49</span><span class="variable">.861</span>  INFO <span class="number">20589</span> --- [   async-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [onSuccess][result: <span class="number">2</span>]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">01</span>:<span class="number">49</span><span class="variable">.861</span>  INFO <span class="number">20589</span> --- [   async-<span class="keyword">task</span>-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.DemoServiceTest</span>     : [onSuccess][result: <span class="number">2</span>]</span><br></pre></td></tr></table></figure></li></ul><h1 id="4-异步异常处理器"><a href="#4-异步异常处理器" class="headerlink" title="4. 异步异常处理器"></a>4. 异步异常处理器</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-simple" target="_blank" rel="noopener">labs-springboot2-async-simple</a> 。</p></blockquote><blockquote><p>友情提示：该示例，基于 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2. 快速入门」</a> 的 <a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-simple" target="_blank" rel="noopener">labs-springboot2-async-simple</a> 的基础上，继续改造。</p></blockquote><h2 id="4-1-GlobalAsyncExceptionHandler"><a href="#4-1-GlobalAsyncExceptionHandler" class="headerlink" title="4.1 GlobalAsyncExceptionHandler"></a>4.1 GlobalAsyncExceptionHandler</h2><p>在 <code>cn.javadog.labs.springboot2.async.simple.core</code> 包路径，创建 <code>GlobalAsyncExceptionHandler</code> 类，全局统一的异步调用异常的处理器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GlobalAsyncExceptionHandler.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GlobalAsyncExceptionHandler</span> <span class="keyword">implements</span> <span class="title">AsyncUncaughtExceptionHandler</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">handleUncaughtException</span><span class="params">(Throwable ex, Method method, Object... params)</span> </span>&#123;</span><br><span class="line">        logger.error(<span class="string">"[handleUncaughtException][method(&#123;&#125;) params(&#123;&#125;) 发生异常]"</span>,</span><br><span class="line">                method, params, ex);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>类上，我们添加了 <code>@Component</code> 注解，考虑到胖友可能会注入一些 Spring Bean 到属性中。</li><li>实现 <code>#handleUncaughtException(Throwable ex, Method method, Object... params)</code> 方法，打印异常日志。这样，后续如果我们接入 ELK ，就可以基于该异常日志进行告警。</li></ul><p><strong>注意</strong>，AsyncUncaughtExceptionHandler 只能拦截<strong>返回类型非 Future</strong> 的异步调用方法。通过看 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java#L295-L321" target="_blank" rel="noopener"><code>AsyncExecutionAspectSupport#handleError(Throwable ex, Method method, Object... params)</code></a> 的源码，可以很容易得到这个结论，代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncExecutionAspectSupport.java</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">handleError</span><span class="params">(Throwable ex, Method method, Object... params)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">	<span class="comment">// 重点！！！如果返回类型是 Future ，则直接抛出该异常。</span></span><br><span class="line">	<span class="keyword">if</span> (Future<span class="class">.<span class="keyword">class</span>.<span class="title">isAssignableFrom</span>(<span class="title">method</span>.<span class="title">getReturnType</span>())) </span>&#123;</span><br><span class="line">		ReflectionUtils.rethrowException(ex);</span><br><span class="line">	&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="comment">// 否则，交给 AsyncUncaughtExceptionHandler 来处理。</span></span><br><span class="line">		<span class="comment">// Could not transmit the exception to the caller with default executor</span></span><br><span class="line">		<span class="keyword">try</span> &#123;</span><br><span class="line">			<span class="keyword">this</span>.exceptionHandler.obtain().handleUncaughtException(ex, method, params);</span><br><span class="line">		&#125; <span class="keyword">catch</span> (Throwable ex2) &#123;</span><br><span class="line">			logger.warn(<span class="string">"Exception handler for async method '"</span> + method.toGenericString() +</span><br><span class="line">					<span class="string">"' threw unexpected exception itself"</span>, ex2);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>对了，AsyncExecutionAspectSupport 是 AsyncExecutionInterceptor 的父类哟。</li></ul><p>所以哟，返回类型为 Future 的异步调用方法，需要通过<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「3. 异步回调」</a>来处理。</p><h2 id="4-2-AsyncConfig"><a href="#4-2-AsyncConfig" class="headerlink" title="4.2 AsyncConfig"></a>4.2 AsyncConfig</h2><p>在 <code>cn.javadog.labs.springboot2.async.simple.config</code> 包路径，创建 <code>AsyncConfig</code> 类，配置异常处理器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span> <span class="comment">// 开启 @Async 的支持</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AsyncConfig</span> <span class="keyword">implements</span> <span class="title">AsyncConfigurer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> GlobalAsyncExceptionHandler exceptionHandler;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> Executor <span class="title">getAsyncExecutor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> AsyncUncaughtExceptionHandler <span class="title">getAsyncUncaughtExceptionHandler</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> exceptionHandler;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在类上添加 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/EnableAsync.java" target="_blank" rel="noopener">@EnableAsync</a> 注解，启用异步功能。这样<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2. Application」</a> 的 <code>@EnableAsync</code> 注解，也就可以去掉了。</li><li>实现 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncConfigurer.java" target="_blank" rel="noopener">AsyncConfigurer</a> 接口，实现异步相关的全局配置。此时此刻，胖友有没想到 SpringMVC 的 <a href="https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java" target="_blank" rel="noopener">WebMvcConfigurer</a> 接口。</li><li>实现 <code>#getAsyncUncaughtExceptionHandler()</code> 方法，返回我们定义的 GlobalAsyncExceptionHandler 对象。</li><li>实现 <code>#getAsyncExecutor()</code> 方法，返回 Spring Task 异步任务的<strong>默认执行器</strong>。这里，我们返回了 <code>null</code> ，并未定义默认执行器。所以最终会使用 <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java" target="_blank" rel="noopener">TaskExecutionAutoConfiguration</a> 自动化配置类创建出来的 ThreadPoolTaskExecutor 任务执行器，作为默认执行器。</li></ul><h2 id="4-3-DemoService"><a href="#4-3-DemoService" class="headerlink" title="4.3 DemoService"></a>4.3 DemoService</h2><p>修改 DemoService 的代码，增加 <code>#zhaoDaoNvPengYou(...)</code> 的异步调用。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Async</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Integer <span class="title">zhaoDaoNvPengYou</span><span class="params">(Integer a, Integer b)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"程序员不需要女朋友"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>直接给想要找女朋友的程序员，抛出该异常。</li></ul><h2 id="4-4-简单测试"><a href="#4-4-简单测试" class="headerlink" title="4.4 简单测试"></a>4.4 简单测试</h2><p>修改 <code>DemoServiceTest</code> 测试类，编写 <code>#testZhaoDaoNvPengYou()</code> 方法，异步调用上述的方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testZhaoDaoNvPengYou</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">    demoService.zhaoDaoNvPengYou(<span class="number">1</span>, <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// sleep 1 秒，保证异步调用的执行</span></span><br><span class="line">    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行单元测试，执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">08</span>:<span class="number">40</span><span class="variable">.890</span> ERROR <span class="number">20620</span> --- [   async-<span class="keyword">task</span>-<span class="number">1</span>] <span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.s</span><span class="variable">.c</span><span class="variable">.GlobalAsyncExceptionHandler</span> : [handleUncaughtException][method(public java<span class="variable">.lang</span><span class="variable">.Integer</span> cn<span class="variable">.javadog</span><span class="variable">.labs</span><span class="variable">.springboot2</span><span class="variable">.async</span><span class="variable">.simple</span><span class="variable">.service</span><span class="variable">.DemoService</span><span class="variable">.zhaoDaoNvPengYou</span>()) params([]) 发生异常]</span><br><span class="line"></span><br><span class="line">java<span class="variable">.lang</span><span class="variable">.RuntimeException</span>: 程序员不需要女朋友</span><br></pre></td></tr></table></figure><ul><li>异步调用的异常成功被 GlobalAsyncExceptionHandler 拦截。</li></ul><h1 id="5-自定义执行器"><a href="#5-自定义执行器" class="headerlink" title="5. 自定义执行器"></a>5. 自定义执行器</h1><blockquote><p>示例代码对应仓库：<a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-advanced" target="_blank" rel="noopener">labs-springboot2-async-advanced</a> 。</p></blockquote><p>在 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2. 快速入门」</a> 中，我们使用 Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java" target="_blank" rel="noopener">TaskExecutionAutoConfiguration</a> 自动化配置类，实现自动配置 ThreadPoolTaskExecutor 任务执行器。</p><blockquote><p>友情提示：考虑到不破坏上面入门的示例，所以我们新建了 <a href="https://github.com/yuyong725/labs-springboot2/tree/master/labs-springboot2-job/labs-springboot2-async-advanced" target="_blank" rel="noopener">labs-springboot2-async-advanced</a> 项目。</p></blockquote><h2 id="5-1-引入依赖"><a href="#5-1-引入依赖" class="headerlink" title="5.1 引入依赖"></a>5.1 引入依赖</h2><p>在 <code>pom.xml</code>文件中，引入相关依赖。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">project</span> <span class="attr">xmlns</span>=<span class="string">"http://maven.apache.org/POM/4.0.0"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">parent</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-job<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>cn.javadog<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>1.0-SNAPSHOT<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">parent</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">modelVersion</span>&gt;</span>4.0.0<span class="tag">&lt;/<span class="name">modelVersion</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>labs-springboot2-async-advanced<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">        <span class="comment">&lt;!-- 引入 Spring Boot 依赖 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">&lt;!-- 方便等会写单元测试 --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;/<span class="name">project</span>&gt;</span></span><br></pre></td></tr></table></figure><ul><li>和 <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.1 引入依赖」</a> 一致。</li></ul><h2 id="5-2-应用配置文件"><a href="#5-2-应用配置文件" class="headerlink" title="5.2 应用配置文件"></a>5.2 应用配置文件</h2><p>在 <code>application.yaml</code> 中，添加 Spring Task 定时任务的配置，如下：</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">task:</span></span><br><span class="line">    <span class="comment"># Spring 执行器配置，对应 TaskExecutionProperties 配置类。对于 Spring 异步任务，会使用该执行器。</span></span><br><span class="line">    <span class="attr">execution-one:</span></span><br><span class="line">      <span class="attr">thread-name-prefix:</span> <span class="string">task-one-</span> <span class="comment"># 线程池的线程名的前缀。默认为 task- ，建议根据自己应用来设置</span></span><br><span class="line">      <span class="attr">pool:</span> <span class="comment"># 线程池相关</span></span><br><span class="line">        <span class="attr">core-size:</span> <span class="number">8</span> <span class="comment"># 核心线程数，线程池创建时候初始化的线程数。默认为 8 。</span></span><br><span class="line">        <span class="attr">max-size:</span> <span class="number">20</span> <span class="comment"># 最大线程数，线程池最大的线程数，只有在缓冲队列满了之后，才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE</span></span><br><span class="line">        <span class="attr">keep-alive:</span> <span class="string">60s</span> <span class="comment"># 允许线程的空闲时间，当超过了核心线程之外的线程，在空闲时间到达之后会被销毁。默认为 60 秒</span></span><br><span class="line">        <span class="attr">queue-capacity:</span> <span class="number">200</span> <span class="comment"># 缓冲队列大小，用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。</span></span><br><span class="line">        <span class="attr">allow-core-thread-timeout:</span> <span class="literal">true</span> <span class="comment"># 是否允许核心线程超时，即开启线程池的动态增长和缩小。默认为 true 。</span></span><br><span class="line">      <span class="attr">shutdown:</span></span><br><span class="line">        <span class="attr">wait-for-tasks-to-complete-on-shutdown:</span> <span class="literal">true</span> <span class="comment"># 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span></span><br><span class="line">        <span class="attr">await-termination-seconds:</span> <span class="number">60</span> <span class="comment"># 等待任务完成的最大时长，单位为秒。默认为 0 ，根据自己应用来设置</span></span><br><span class="line">    <span class="comment"># Spring 执行器配置，对应 TaskExecutionProperties 配置类。对于 Spring 异步任务，会使用该执行器。</span></span><br><span class="line">    <span class="attr">execution-two:</span></span><br><span class="line">      <span class="attr">thread-name-prefix:</span> <span class="string">task-two-</span> <span class="comment"># 线程池的线程名的前缀。默认为 task- ，建议根据自己应用来设置</span></span><br><span class="line">      <span class="attr">pool:</span> <span class="comment"># 线程池相关</span></span><br><span class="line">        <span class="attr">core-size:</span> <span class="number">8</span> <span class="comment"># 核心线程数，线程池创建时候初始化的线程数。默认为 8 。</span></span><br><span class="line">        <span class="attr">max-size:</span> <span class="number">20</span> <span class="comment"># 最大线程数，线程池最大的线程数，只有在缓冲队列满了之后，才会申请超过核心线程数的线程。默认为 Integer.MAX_VALUE</span></span><br><span class="line">        <span class="attr">keep-alive:</span> <span class="string">60s</span> <span class="comment"># 允许线程的空闲时间，当超过了核心线程之外的线程，在空闲时间到达之后会被销毁。默认为 60 秒</span></span><br><span class="line">        <span class="attr">queue-capacity:</span> <span class="number">200</span> <span class="comment"># 缓冲队列大小，用来缓冲执行任务的队列的大小。默认为 Integer.MAX_VALUE 。</span></span><br><span class="line">        <span class="attr">allow-core-thread-timeout:</span> <span class="literal">true</span> <span class="comment"># 是否允许核心线程超时，即开启线程池的动态增长和缩小。默认为 true 。</span></span><br><span class="line">      <span class="attr">shutdown:</span></span><br><span class="line">        <span class="attr">wait-for-tasks-to-complete-on-shutdown:</span> <span class="literal">true</span> <span class="comment"># 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true</span></span><br><span class="line">        <span class="attr">await-termination-seconds:</span> <span class="number">60</span> <span class="comment"># 等待任务完成的最大时长，单位为秒。默认为 0 ，根据自己应用来设置</span></span><br></pre></td></tr></table></figure><ul><li>在 <code>spring.task</code> 配置项下，我们新增了 <code>execution-one</code> 和 <code>execution-two</code> 两个执行器的配置。在格式上，我们保持和在<a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-job/">「2.7 应用配置文件」</a>看到的 <code>spring.task.exeuction</code> 一致，方便我们后续复用 TaskExecutionProperties 属性配置类来映射。</li></ul><h2 id="5-3-AsyncConfig"><a href="#5-3-AsyncConfig" class="headerlink" title="5.3 AsyncConfig"></a>5.3 AsyncConfig</h2><p>在 <code>cn.javadog.labs.springboot2.async.advanced.config</code> 包路径，创建 <code>AsyncConfig</code> 类，配置两个执行器。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// AsyncConfig.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAsync</span> <span class="comment">// 开启 @Async 的支持</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AsyncConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXECUTOR_ONE_BEAN_NAME = <span class="string">"executor-one"</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXECUTOR_TWO_BEAN_NAME = <span class="string">"executor-two"</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ExecutorOneConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span>(name = EXECUTOR_ONE_BEAN_NAME + <span class="string">"-properties"</span>)</span><br><span class="line">        <span class="meta">@Primary</span></span><br><span class="line">        <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.task.execution-one"</span>) <span class="comment">// 读取 spring.task.execution-one 配置到 TaskExecutionProperties 对象</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> TaskExecutionProperties <span class="title">taskExecutionProperties</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> TaskExecutionProperties();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span>(name = EXECUTOR_ONE_BEAN_NAME)</span><br><span class="line">        <span class="function"><span class="keyword">public</span> ThreadPoolTaskExecutor <span class="title">threadPoolTaskExecutor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 创建 TaskExecutorBuilder 对象</span></span><br><span class="line">            TaskExecutorBuilder builder = createTskExecutorBuilder(<span class="keyword">this</span>.taskExecutionProperties());</span><br><span class="line">            <span class="comment">// 创建 ThreadPoolTaskExecutor 对象</span></span><br><span class="line">            <span class="keyword">return</span> builder.build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Configuration</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ExecutorTwoConfiguration</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span>(name = EXECUTOR_TWO_BEAN_NAME + <span class="string">"-properties"</span>)</span><br><span class="line">        <span class="meta">@ConfigurationProperties</span>(prefix = <span class="string">"spring.task.execution-two"</span>) <span class="comment">// 读取 spring.task.execution-two 配置到 TaskExecutionProperties 对象</span></span><br><span class="line">        <span class="function"><span class="keyword">public</span> TaskExecutionProperties <span class="title">taskExecutionProperties</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> TaskExecutionProperties();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Bean</span>(name = EXECUTOR_TWO_BEAN_NAME)</span><br><span class="line">        <span class="function"><span class="keyword">public</span> ThreadPoolTaskExecutor <span class="title">threadPoolTaskExecutor</span><span class="params">()</span> </span>&#123;</span><br><span class="line">            <span class="comment">// 创建 TaskExecutorBuilder 对象</span></span><br><span class="line">            TaskExecutorBuilder builder = createTskExecutorBuilder(<span class="keyword">this</span>.taskExecutionProperties());</span><br><span class="line">            <span class="comment">// 创建 ThreadPoolTaskExecutor 对象</span></span><br><span class="line">            <span class="keyword">return</span> builder.build();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> TaskExecutorBuilder <span class="title">createTskExecutorBuilder</span><span class="params">(TaskExecutionProperties properties)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// Pool 属性</span></span><br><span class="line">        TaskExecutionProperties.Pool pool = properties.getPool();</span><br><span class="line">        TaskExecutorBuilder builder = <span class="keyword">new</span> TaskExecutorBuilder();</span><br><span class="line">        builder = builder.queueCapacity(pool.getQueueCapacity());</span><br><span class="line">        builder = builder.corePoolSize(pool.getCoreSize());</span><br><span class="line">        builder = builder.maxPoolSize(pool.getMaxSize());</span><br><span class="line">        builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());</span><br><span class="line">        builder = builder.keepAlive(pool.getKeepAlive());</span><br><span class="line">        <span class="comment">// Shutdown 属性</span></span><br><span class="line">        TaskExecutionProperties.Shutdown shutdown = properties.getShutdown();</span><br><span class="line">        builder = builder.awaitTermination(shutdown.isAwaitTermination());</span><br><span class="line">        builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());</span><br><span class="line">        <span class="comment">// 其它基本属性</span></span><br><span class="line">        builder = builder.threadNamePrefix(properties.getThreadNamePrefix());</span><br><span class="line"><span class="comment">//        builder = builder.customizers(taskExecutorCustomizers.orderedStream()::iterator);</span></span><br><span class="line"><span class="comment">//        builder = builder.taskDecorator(taskDecorator.getIfUnique());</span></span><br><span class="line">        <span class="keyword">return</span> builder;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>参考 Spring Boot <a href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.java" target="_blank" rel="noopener">TaskExecutionAutoConfiguration</a> 自动化配置类，我们创建了 ExecutorOneConfiguration 和 ExecutorTwoConfiguration 配置类，来分别创建 Bean 名字为 <code>executor-one</code> 和 <code>executor-two</code> 两个执行器。</li></ul><h2 id="5-4-DemoService"><a href="#5-4-DemoService" class="headerlink" title="5.4 DemoService"></a>5.4 DemoService</h2><p>在 <code>cn.javadog.labs.springboot2.async.advanced.service</code> 包路径下，创建 <code>DemoService</code> 类。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoService.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DemoService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Logger logger = LoggerFactory.getLogger(getClass());</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Async</span>(AsyncConfig.EXECUTOR_ONE_BEAN_NAME)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Integer <span class="title">execute01</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[execute01]"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Async</span>(AsyncConfig.EXECUTOR_TWO_BEAN_NAME)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> Integer <span class="title">execute02</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"[execute02]"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>在 <code>@Async</code> 注解上，我们设置了其使用的执行器的 Bean 名字。</li></ul><h2 id="5-5-简单测试"><a href="#5-5-简单测试" class="headerlink" title="5.5 简单测试"></a>5.5 简单测试</h2><p>创建 <code>DemoServiceTest</code> 测试类，编写 <code>#testExecute()</code> 方法，异步调用 DemoService 的上述两个方法。代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// DemoServiceTest.java</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@RunWith</span>(SpringRunner<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class">@<span class="title">SpringBootTest</span>(<span class="title">classes</span> </span>= Application<span class="class">.<span class="keyword">class</span>)</span></span><br><span class="line"><span class="class"><span class="title">public</span> <span class="title">class</span> <span class="title">DemoServiceTest</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> DemoService demoService;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testExecute</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        demoService.execute01();</span><br><span class="line">        demoService.execute02();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// sleep 1 秒，保证异步调用的执行</span></span><br><span class="line">        Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行单元测试，执行日志如下：</p><figure class="highlight verilog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">14</span>:<span class="number">48</span><span class="variable">.998</span>  INFO <span class="number">20653</span> --- [     <span class="keyword">task</span>-one-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.advanced</span><span class="variable">.service</span><span class="variable">.DemoService</span>   : [execute01]</span><br><span class="line"><span class="number">2020</span>-<span class="number">02</span>-<span class="number">18</span> <span class="number">17</span>:<span class="number">14</span>:<span class="number">48</span><span class="variable">.998</span>  INFO <span class="number">20653</span> --- [     <span class="keyword">task</span>-two-<span class="number">1</span>] c<span class="variable">.j</span><span class="variable">.l</span><span class="variable">.s</span><span class="variable">.a</span><span class="variable">.advanced</span><span class="variable">.service</span><span class="variable">.DemoService</span>   : [execute02]</span><br></pre></td></tr></table></figure><ul><li>从日志中，我们可以看到，<code>#execute01()</code> 方法在 <code>executor-one</code> 执行器中执行，而 <code>#execute02()</code> 方法在 <code>executor-two</code> 执行器中执行。符合预期~</li></ul><h1 id="666-彩蛋"><a href="#666-彩蛋" class="headerlink" title="666. 彩蛋"></a>666. 彩蛋</h1><p>如果胖友使用 Spring Task 的异步任务，一定要注意两个点：</p><ul><li>JVM 应用的正常优雅关闭，保证异步任务都被执行完成。</li><li>编写异步异常处理器 GlobalAsyncExceptionHandler ，记录异常日志，进行监控告警。</li></ul><p>嗯<del>~</del>如果觉得不过瘾的胖友，可以再去看看 <a href="https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/integration.html#scheduling" target="_blank" rel="noopener">《Spring Framework Documentation —— Task Execution and Scheduling》</a> 文档。</p><p>不过呢，Spring Task 异步任务，在项目中使用的并不多，更多的选择，还是可靠的分布式队列。当然，「芋道源码」在自己的开源项目 <a href="https://github.com/YunaiV/onemall" target="_blank" rel="noopener">onemall</a> 中，使用 <a href="https://github.com/YunaiV/onemall/blob/74724637b7408461e6570855172c753337293b30/common/mall-spring-boot/src/main/java/cn/iocoder/mall/spring/boot/web/interceptor/AccessLogInterceptor.java" target="_blank" rel="noopener">AccessLogInterceptor</a> 拦截器，记录访问日志到数据库。因为访问日志更多是用于监控和排查问题，所以即使有一定的丢失，影响也不大。</p></div><div class="article-footer"><blockquote class="mt-2x"><ul class="post-copyright list-unstyled"><li class="post-copyright-link hidden-xs"><strong>本文链接：</strong> <a href="https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-Job/" title="Spring Boot 异步任务入门" target="_blank" rel="external">https://calm_java.gitee.io/blog/2020/02/18/labs-springboot2-async-Job/</a></li><li class="post-copyright-license"><strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="http://creativecommons.org/licenses/by/4.0/deed.zh" target="_blank" rel="external">CC BY 4.0 CN协议</a> 许可协议。转载请注明出处！</li></ul></blockquote><div class="panel panel-default panel-badger"><div class="panel-body"><figure class="media"><div class="media-left"><a href="https://github.com/yuyong725" target="_blank" class="img-burn thumb-sm visible-lg"><img src="/blog/images/avatar.jpg" class="img-rounded w-full" alt=""></a></div><div class="media-body"><h3 class="media-heading"><a href="https://github.com/yuyong725" target="_blank"><span class="text-dark">卡姆</span><small class="ml-1x">a Java coder need calm</small></a></h3><div>享受编程</div></div></figure></div></div></div></article><section id="comments"></section></div><nav class="bar bar-footer clearfix" data-stick-bottom><div class="bar-inner"><ul class="pager pull-left"><li class="next"><a href="/blog/2020/02/18/labs-springboot2-job/" title="Spring Boot 定时任务入门"><span>下一篇&nbsp;&nbsp;</span><i class="icon icon-angle-right" aria-hidden="true"></i></a></li><li class="toggle-toc"><a class="toggle-btn" data-toggle="collapse" href="#collapseToc" aria-expanded="true" title="文章目录" role="button"><span>[&nbsp;</span><span>文章目录</span> <i class="text-collapsed icon icon-anchor"></i> <i class="text-in icon icon-close"></i> <span>]</span></a></li></ul><button type="button" class="btn btn-fancy btn-donate pop-onhover bg-gradient-warning" data-toggle="modal" data-target="#donateModal"><span>赏</span></button><div class="bar-right"><div class="share-component" data-sites="wechat" data-mobile-sites="wechat"></div></div></div></nav><div class="modal modal-center modal-small modal-xs-full fade" id="donateModal" tabindex="-1" role="dialog"><div class="modal-dialog" role="document"><div class="modal-content donate"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><div class="modal-body"><div class="donate-box"><div class="donate-head"><p>感谢您的支持，我会继续努力的!</p></div><div class="tab-content"><div role="tabpanel" class="tab-pane fade active in" id="alipay"><div class="donate-payimg"><img src="/blog/images/donate/alipayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开支付宝扫一扫，即可进行扫码打赏哦</p></div><div role="tabpanel" class="tab-pane fade" id="wechatpay"><div class="donate-payimg"><img src="/blog/images/donate/wechatpayimg.jpeg" alt="扫码支持" title="扫一扫"></div><p class="text-muted mv">扫码打赏，你说多少就多少</p><p class="text-grey">打开微信扫一扫，即可进行扫码打赏哦</p></div></div><div class="donate-footer"><ul class="nav nav-tabs nav-justified" role="tablist"><li role="presentation" class="active"><a href="#alipay" id="alipay-tab" role="tab" data-toggle="tab" aria-controls="alipay" aria-expanded="true"><i class="icon icon-alipay"></i> 支付宝</a></li><li role="presentation"><a href="#wechatpay" role="tab" id="wechatpay-tab" data-toggle="tab" aria-controls="wechatpay" aria-expanded="false"><i class="icon icon-wepay"></i> 微信支付</a></li></ul></div></div></div></div></div></div></main><footer class="footer" itemscope itemtype="http://schema.org/WPFooter"></footer><script src="//cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script><script>window.jQuery||document.write('<script src="js/jquery.min.js"><\/script>')</script><script src="/blog/js/plugin.min.js"></script><script src="/blog/js/application.js"></script><script>!function(T){var N={TRANSLATION:{POSTS:"文章",PAGES:"页面",CATEGORIES:"分类",TAGS:"标签",UNTITLED:"(未命名)"},ROOT_URL:"/blog/",CONTENT_URL:"/blog/content.json"};T.INSIGHT_CONFIG=N}(window)</script><script src="/blog/js/insight.js"></script><script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script src="//cdn.jsdelivr.net/npm/gitalk@1.4.0/dist/gitalk.min.js"></script><script src="//cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script><script type="text/javascript">var gitalk=new Gitalk({clientID:"adf0c8bfdd8796029917",clientSecret:"a88c2e2e83e2b180f62ad927dcb347871dfe9f4b",repo:"myblog-comment",owner:"yuyong725",admin:["yuyong725"],id:md5(location.pathname),distractionFreeMode:!0});gitalk.render("comments")</script></body></html><!-- rebuild by neat -->